package com.utilities.pipelineScripts;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

import com.utilities.IO.FileUtilities;
import com.utilities.dataStructures.Range;
import com.utilities.parsers.ContigsStatsGenerator;

public class PiplineAnalyser 
{
	private int seqLength = 0;
	// this is the file containing the ranges of all the 
	// possible genes. This is extracted from the genebankFile
	private String geneRangeFile = null;
	private String genesFoundRangeFile = null;
	// file without been perturbed by the mapper
	private String initialContigsFile = null;
	private String includedContigsFile = null;
	private String excludedContigsFile = null;
	private String resultGff3File = null;
	private String analysisSummryFile = null;

	int initial_gc, initial_contigLength , initial_reads,
	excluded_gc, excluded_contigLength, excluded_reads, 
	included_gc, included_contigLength, included_gapSize,
	included_MappedLength, included_reads;
	double mergedContigsMappedLength = 0;
	ArrayList<Range> noGenesGaps, intial_contigs, excludedContigs,
	includedContigs, gaps, allGenes, mergedContigs, allGenesFoundInContigs,
	missingGenes, GenesInGapsButFound;
	
	// files to be written
	String noGenesGaps_file, mergedContigs_file, missingGenes_file, 
	GenesInGapsButFound_file = null;
	
	String workspace = null;


	private HashMap<Range, ArrayList<Range>> hashMap = null;

	/**
	 * @param args
	 * args[0]	analysis summry file
	 * args[1]	excluded contigs file
	 * args[2]	gene Range file
	 * args[3]	gene found range file
	 * args[4]	included contigs file
	 * args[5]	initial contigs file
	 * args[6]	result gff3 file
	 * args[7]	workspace
	 * args[8]	seqlength file
	 */
	public static void main(String[] args) 
	{
		String seqL = null;
		try
		{
			BufferedReader bufferedReader = new BufferedReader(new FileReader(args[8]));
			seqL = bufferedReader.readLine();
			if (seqL == null)
			{
				return;
			}
			boolean result = new PiplineAnalyser().setAnalysisSummryFile(args[0]).setExcludedContigsFile(args[1])
			.setGeneRangeFile(args[2]).setGenesFoundRangeFile(args[3])
			.setIncludedContigsFile(args[4]).setInitialContigsFile(args[5])
			.setResultGff3File(args[6]).setWorkspace(args[7]).setSeqLength(Integer.parseInt(seqL)).analyse();
			if (!result)
			{
				System.out.println(result);
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
	}

	public boolean analyse() throws Exception
	{
		if (workspace == null) return false;
		
		// get info about the intial contigs
		ContigsStatsGenerator generator = ContigsStatsGenerator.getInstance()
		.setInputScaffoldFile(workspace + initialContigsFile).setSeqReadLength(seqLength).setConsiderGaps(false);
		if (!generator.generateStats()) return false;
		intial_contigs = generator.getEachContigInfo();
		initial_gc = generator.getTotalCGContent();
		initial_contigLength = generator.getTotalContigLength();
		initial_reads = generator.getTotalNumberOfReads();

		// get info about the contigs that are left out
		generator = ContigsStatsGenerator.getInstance().setInputScaffoldFile(workspace + excludedContigsFile)
		.setSeqReadLength(seqLength).setConsiderGaps(false);
		if (!generator.generateStats()) return false;
		excludedContigs = generator.getEachContigInfo();
		excluded_gc = generator.getTotalCGContent();
		excluded_contigLength = generator.getTotalContigLength();
		excluded_reads = generator.getTotalNumberOfReads();

		// get info about the contigs that are actually included
		generator = ContigsStatsGenerator.getInstance().setInputScaffoldFile(workspace + includedContigsFile)
		.setSeqReadLength(seqLength).setConsiderGaps(true);
		if (!generator.generateStats()) return false;
		includedContigs = generator.getEachContigInfo();
		gaps = generator.getEachGapInfo();
		included_gc = generator.getTotalCGContent();
		included_contigLength = generator.getTotalContigLength();
		included_gapSize = generator.getTotalGapSize();
		included_MappedLength = generator.getTotalMappedLength();
		included_reads = generator.getTotalNumberOfReads();


		allGenes = FileUtilities.readFromFile(workspace + geneRangeFile);
		// this call initialises the hashmap which is the map of gap ranges to the possible
		// genes in them. since we are also interested in finding the gaps where no genes were present
		// therefore this method has been written and named as such (could be misleading) :D
		noGenesGaps = findGenesInGaps(gaps, allGenes);
		mergedContigs = mergeOverLappingContigs(includedContigs);
		for (Range contig : mergedContigs)
		{
			mergedContigsMappedLength = mergedContigsMappedLength + contig.getActualLength();
		}
		ArrayList<ArrayList<Range>> lists = filterGenesFoundAndNotFoundInGaps(workspace + resultGff3File);

		// typically the sum of all these two should be equal to the allGenes 
		allGenesFoundInContigs = lists.get(2);
		missingGenes = lists.get(1);
		// Genes found in gap but found somewhere else
		GenesInGapsButFound = lists.get(0);
		
		FileUtilities.WriteToFile(noGenesGaps, workspace + noGenesGaps_file);
		FileUtilities.WriteToFile(mergedContigs, workspace + mergedContigs_file);
		FileUtilities.WriteToFile(missingGenes, workspace + missingGenes_file);
		FileUtilities.WriteToFile(GenesInGapsButFound, workspace + GenesInGapsButFound_file);
		toXML();
		return true;
	}

	private void toXML() throws FileNotFoundException {
		OutputStream out = null;
		if (analysisSummryFile == null)
		{
			out = System.out;
		}
		else
		{
			out = new FileOutputStream(workspace + analysisSummryFile);
		}

		PrintWriter pw = new PrintWriter(out);
		pw.println("<analysisData>");
		pw.println("<refSize>" + seqLength + "</refSize>");
		pw.println("<intialContigsData>");
		pw.println("<cgs> " + initial_gc + "</cgs>");
		pw.println("<contigs>" + intial_contigs.size() + "</contigs>");
		pw.println("<contigLength> " + initial_contigLength + "</contigLength>");
		pw.println("<reads> " + initial_reads + "</reads>");
		pw.println("</intialContigsData>");
		pw.println("<excludedContigsData>");
		pw.println("<cgs> " + excluded_gc + "</cgs>");
		pw.println("<contigs>" + excludedContigs.size() + "</contigs>");
		pw.println("<contigLength> " + excluded_contigLength + "</contigLength>");
		pw.println("<reads> " + excluded_reads + "</reads>");
		pw.println("</excludedContigsData>");
		pw.println("<includedContigsData>");
		pw.println("<cgs> " + included_gc + "</cgs>");
		pw.println("<contigs>" + includedContigs.size() + "</contigs>");
		pw.println("<contigLength> " + included_contigLength + "</contigLength>");
		pw.println("<reads> " + included_reads + "</reads>");
		pw.println("<mappedLength> " + included_MappedLength + "</mappedLength>");
		pw.println("<gaps> " + gaps.size() + "</gaps>");
		pw.println("<gapLength> " + included_gapSize + "</gapLength>");
		pw.println("</includedContigsData>");
		pw.println("<mergedContigsData>");
		pw.println("<contigs>" +  mergedContigs.size() + "</contigs>");
		pw.println("<mappedLength> " + (int) mergedContigsMappedLength + "</mappedLength>");
		pw.println("</mergedContigsData>");
		pw.println("<genesData>");
		pw.println("<genesPossible>" +  allGenes.size() + "</genesPossible>");
		pw.println("<gapsNoGenes>" +  noGenesGaps.size() + "</gapsNoGenes>");
		pw.println("<genesFound>" +  allGenesFoundInContigs.size() + "</genesFound>");
		pw.println("<genesMissing>" +  missingGenes.size() + "</genesMissing>");
		pw.println("<genesInGapsButFound>" +  GenesInGapsButFound.size() + "</genesInGapsButFound>");
		pw.println("</genesData>");
		pw.println("");
		pw.println("</analysisData>");
		//		pw.println("Total Mapped Length of Gaps with no genes possible");
		pw.flush();
		pw.close();
	}

	private ArrayList<Range> findGenesInGaps(ArrayList<Range> gaps, ArrayList<Range> allGenes) 
	throws IOException
	{
		ArrayList<Range> noGenesGaps = new ArrayList<Range>();
		hashMap = new HashMap<Range, ArrayList<Range>>();
		for (Range range : gaps)
		{
			ArrayList<Range> overLap = new ArrayList<Range>();
			for (Range gene : allGenes)
			{
				if (gene.getLowerLimit() > range.getUpperLimit())
				{
					break;
				}
				else
				{
					if (!gene.exclusive(range))
					{
						overLap.add(gene);
					}
				}
			}
			if (overLap.size() > 0) hashMap.put(range, overLap);
			else noGenesGaps.add(range);
		}
		Collections.sort(noGenesGaps,new RangeComparator());
		return noGenesGaps;
	}
	private ArrayList<Range> mergeOverLappingContigs(ArrayList<Range> contigsList)
	{
		ArrayList<Range> result = new ArrayList<Range>();

		for (Range contigRange : contigsList)
		{
			int start = contigRange.getLowerLimit();
			if (result.size() > 0)
			{
				Range lastRange = result.get(result.size()  - 1);
				int previousEnd = lastRange.getUpperLimit();
				if (previousEnd >= start || (previousEnd + 1) == start )
				{
					int overLap = start - previousEnd - 1;
					int overLapcounter = result.size();
					lastRange = result.remove(result.size()  - 1);
					String name = lastRange.getMetadata();
					Range range = null;
					if (name.contains("_overLap"))
					{
						name = name.split("_overLap")[0];
						range = new Range(lastRange.getLowerLimit(), contigRange.getUpperLimit(), 
								name + "_" + contigRange.getMetadata() + " _overLap" + overLapcounter);
					}
					else
					{
						range = new Range(lastRange.getLowerLimit(), contigRange.getUpperLimit(), 
								lastRange.getMetadata() + "_" + contigRange.getMetadata() + " _overLap" + overLapcounter);
					}
					range = range.setActualLength(lastRange.getActualLength() + overLap + contigRange.getActualLength());
					result.add(range);
				}
				else
				{
					result.add(contigRange);
				}
			}
			else
			{
				result.add(contigRange);
			}
		}
		return result;
	}

	public ArrayList<ArrayList<Range>> filterGenesFoundAndNotFoundInGaps(String resultGff3File) throws Exception
	{
		ArrayList<ArrayList<Range>> result = new ArrayList<ArrayList<Range>>();
		ArrayList<Range> missingGenesList = new ArrayList<Range>();
		ArrayList<Range> foundGenesList = new ArrayList<Range>();

		// all genes that were found in the sequence
		ArrayList<Range> genesFound = FileUtilities.readFromFile(genesFoundRangeFile);
		Collections.sort(genesFound,new RangeComparator());

		// all gaps containing possible genes
		ArrayList<Range> gapsWithGenes = new ArrayList<Range>(hashMap.keySet());
		Collections.sort(gapsWithGenes,new RangeComparator());

		for (Range gap : gapsWithGenes)
		{
			// list of genes possible in this gap
			ArrayList<Range> possibleGenes = hashMap.get(gap);
			for (Range possibleGene : possibleGenes)
			{
				Range found = null;
				// check if we have already found this gene somewhere else or not
				for (Range geneFound : genesFound)
				{
					if (geneFound.getMetadata().equalsIgnoreCase(possibleGene.getMetadata()))
					{
						found = geneFound; break;
					}
				}
				// if we have found this gene somewhere else then write this
				if (found != null)
				{
					foundGenesList.add(found);
				}
				// if we haven't accounted for this gene then write this
				else
				{
					missingGenesList.add(possibleGene);
				}
			}
		}
		result.add(foundGenesList);
		result.add(missingGenesList);
		// total genes
		result.add(genesFound);
		return result;
	}

	public PiplineAnalyser setSeqLength(int seqLength) {
		this.seqLength = seqLength;
		return this;
	}

	public PiplineAnalyser setGeneRangeFile(String geneRangeFile) {
		this.geneRangeFile = geneRangeFile;
		return this;
	}

	public PiplineAnalyser setGenesFoundRangeFile(String genesFoundRangeFile) {
		this.genesFoundRangeFile = genesFoundRangeFile;
		return this;
	}

	public PiplineAnalyser setInitialContigsFile(String initialContigsFile) {
		this.initialContigsFile = initialContigsFile;
		return this;
	}

	public PiplineAnalyser setIncludedContigsFile(String includedContigsFile) {
		this.includedContigsFile = includedContigsFile;
		return this;
	}

	public PiplineAnalyser setExcludedContigsFile(String excludedContigsFile) {
		this.excludedContigsFile = excludedContigsFile;
		return this;
	}

	public PiplineAnalyser setResultGff3File(String resultGff3File) {
		this.resultGff3File = resultGff3File;
		return this;
	}

	public PiplineAnalyser setAnalysisSummryFile(String analysisSummryFile) {
		this.analysisSummryFile = analysisSummryFile;
		return this;
	}
	
	public PiplineAnalyser setWorkspace(String workspace) {
		this.workspace = workspace + "/";
		return this;
	}
}
